package com.xiyan.base.config;

import com.xiyan.base.entity.User;
import com.xiyan.base.service.IUserService;
import com.xiyan.base.utils.Constant;
import com.xiyan.base.utils.JWTUtil;
import com.xiyan.base.utils.RedisUtil;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.SimpleByteSource;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Set;

/**
 * @program: shiro
 * @description: 授权与认证方法，并继承AuthorizingRealm
 * @author: ruiyanglee
 * @create: 2020-07-23 16:19
 **/
public class MyRealm extends AuthorizingRealm {

    @Autowired
    private IUserService userService;

    @Autowired
    private RedisUtil redisUtil;

    /**
     * 必须重写此方法，不然Shiro会报错
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JWTToken;
    }

    /**
     * 获取权限信息方法
     *
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();

        // 获取用户名
        User currentUser = (User) principalCollection.getPrimaryPrincipal();
        String username = currentUser.getUsername();
        // 从数据库中获取用户角色
        Set<String> roleList = userService.findRolesByUsername(username);
        if (roleList != null && roleList.size() > 0) {
            simpleAuthorizationInfo.setRoles(roleList);
        }
        // 获取权限
        Set<String> permissionList = userService.findPermissionsByUsername(username);
        if (permissionList != null && permissionList.size() > 0) {
            simpleAuthorizationInfo.setStringPermissions(permissionList);
        }
        return simpleAuthorizationInfo;
    }

    /**
     * 获取认证信息方法
     *
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

        // 校验token有效性
        String token = (String) authenticationToken.getCredentials();
        String username = JWTUtil.getUsername(token);

        if (username == null || username.length() < 1) {
            throw new AuthenticationException("token非法无效!");
        }

        // 获取用户
        User user = userService.findByUsername(username);

        // 查询用户信息
        if (user == null) {
            throw new AuthenticationException("用户不存在!");
        }

        // jwt
        if (username == null || !JWTUtil.verify(token, username)) {
            throw new AuthenticationException("token认证失败！");
        }

        // 校验token是否超时失效 & 或者账号密码是否错误, 并进行token刷新
        if (!jwtTokenRefresh(token, username, user.getPassword())) {
            throw new AuthenticationException("Token失效，请重新登录!");
        }


//        return new SimpleAuthenticationInfo(user, token, getName());

        // 封装AuthenticationInfo
        SimpleByteSource bsSalt = new SimpleByteSource(Constant.PRIVATE_SALT);
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, token, getName());
        return info;
    }


    /**
     * JWTToken刷新生命周期 （实现： 用户在线操作不掉线功能）
     * 1、登录成功后将用户的JWT生成的Token作为k、v存储到cache缓存里面(这时候k、v值一样)，缓存有效期设置为Jwt有效时间的2倍
     * 2、当该用户再次请求时，通过JWTFilter层层校验之后会进入到doGetAuthenticationInfo进行身份验证
     * 3、当该用户这次请求jwt生成的token值已经超时，但该token对应cache中的k还是存在，则表示该用户一直在操作只是JWT的token失效了，程序会给token对应的k映射的v值重新生成JWTToken并覆盖v值，该缓存生命周期重新计算
     * 4、当该用户这次请求jwt在生成的token值已经超时，并在cache中不存在对应的k，则表示该用户账户空闲超时，返回用户信息已失效，请重新登录。
     * 注意： 前端请求Header中设置Authorization保持不变，校验有效性以缓存中的token为准。
     * 用户过期时间 = Jwt有效时间 * 2。
     *
     * @param userName
     * @return
     */
    public boolean jwtTokenRefresh(String token, String userName, String passWord) {
        String cacheToken = String.valueOf(redisUtil.get(Constant.TOKEN_PREFIX + userName));
        System.out.println("token:" + cacheToken);
        if (token != null && !token.equals(cacheToken)) {
            return false;
        }
        if (cacheToken != null && !cacheToken.equals("")
                && !cacheToken.equals("null")) {
            // 校验token有效性
            if (!JWTUtil.verify(cacheToken, userName)) {
                String newAuthorization = JWTUtil.sign(userName);
                // 设置超时时间
                redisUtil.set(Constant.TOKEN_PREFIX + userName, newAuthorization);
                redisUtil.expire(Constant.EXPIRE_TIME + userName, Constant.EXPIRE_TIME);
            }
            return true;
        }
        return false;
    }
}
